package com.htl.exampro.user.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.htl.exampro.base.exception.ExamProException;
import com.htl.exampro.base.model.PageResult;
import com.htl.exampro.base.utils.JwtUtil;
import com.htl.exampro.base.utils.StringUtil;
import com.htl.exampro.base.utils.UserUtil;
import com.htl.exampro.user.mapper.UserMapper;
import com.htl.exampro.user.pojo.domain.UserDO;
import com.htl.exampro.user.pojo.query.UserPageParam;
import com.htl.exampro.user.pojo.vo.UserVO;
import com.htl.exampro.user.service.UserService;
import com.htl.exampro.user.util.EmailUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 用户服务实现类
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements UserService {

    private static final String REGISTER_CODE = "REGISTER_CODE_"; //注册验证码前缀

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private EmailUtil emailUtil;

    @Override
    public PageResult<UserVO> getUserList(UserPageParam pageParam) {
        Page<UserDO> page = new Page<>(pageParam.getPageNumber(), pageParam.getPageSize());
        LambdaQueryWrapper<UserDO> wrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(pageParam.getUsername())) {
            wrapper.like(UserDO::getUsername, pageParam.getUsername())
                    .or()
                    .like(UserDO::getRealName, pageParam.getUsername());
        }
        wrapper.eq(StringUtils.isNotBlank(pageParam.getPhone()), UserDO::getPhone, pageParam.getPhone());
        wrapper.eq(StringUtils.isNotBlank(pageParam.getEmail()), UserDO::getEmail, pageParam.getEmail());
        wrapper.orderByDesc(UserDO::getCreatedTime);
        PageResult<UserVO> result = PageResult.result(page(page, wrapper), UserVO.class);
        result.getData().forEach(o -> o.setPassword(null));
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createUser(UserVO userVO) {
        String code = userVO.getCode();
        if (StringUtils.isEmpty(code)) {
            ExamProException.cast("验证码不能为空！");
        }
        String codeSave = null;
        if (StringUtils.isNotBlank(userVO.getPhone())) {
            codeSave = redisTemplate.opsForValue().get(REGISTER_CODE + userVO.getPhone());
        } else if (StringUtils.isNotBlank(userVO.getEmail())) {
            codeSave = redisTemplate.opsForValue().get(REGISTER_CODE + userVO.getEmail());
        }

        if (StringUtils.isEmpty(codeSave)) {
            ExamProException.cast("验证码已失效！请重新获取验证码。");
        }
        if (!code.equals(codeSave)) {
            ExamProException.cast("验证码错误！请重新输入验证码。");
        }

        // 加密密码
        UserDO userDO = BeanUtil.toBean(userVO, UserDO.class);
        userDO.setPassword(passwordEncoder.encode(userVO.getPassword()));
        save(userDO);

        if (StringUtils.isNotBlank(userVO.getPhone())) {
            redisTemplate.delete(REGISTER_CODE + userVO.getPhone());
        } else if (StringUtils.isNotBlank(userVO.getEmail())) {
            redisTemplate.delete(REGISTER_CODE + userVO.getEmail());
        }
    }

    @Override
    public UserVO getUserDetail(Long id) {
        UserVO userVO = BeanUtil.toBean(getById(id), UserVO.class);
        userVO.setPassword(null);
        return userVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUser(UserVO user) {
        // 如果密码不为空则加密
        if (StringUtils.isNotBlank(user.getPassword())) {
            user.setPassword(passwordEncoder.encode(user.getPassword()));
        }
        UserDO userDO = BeanUtil.toBean(user, UserDO.class);
        updateById(userDO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUserStatus(UserVO user) {
        UserDO userDO = BeanUtil.toBean(user, UserDO.class);
        updateById(userDO);
        redisTemplate.opsForValue().getOperations().delete(UserUtil.getTokenKey(userDO.getUserId()));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteUser(Long id) {
        removeById(id);
    }

    @Override
    public UserVO login(String username, String password, String remoteAddr) {
        // 查询用户
        UserDO userDO = getOne(new LambdaQueryWrapper<UserDO>()
                .eq(UserDO::getUsername, username));
        if (userDO == null) {
            throw new ExamProException("用户不存在");
        }
        // 校验密码
        if (!passwordEncoder.matches(password, userDO.getPassword())) {
            throw new ExamProException("密码错误");
        }
        // 校验状态
        if (!userDO.getStatus()) {
            throw new ExamProException("用户已被禁用");
        }
        // 更新登录信息
        userDO.setLastLoginTime(LocalDateTime.now());
        userDO.setLastLoginIp(remoteAddr);
        userDO.setUpdatedPerson(userDO.getUserId().toString());
        updateById(userDO);
        Map<String, Object> claims = new HashMap<>();
        claims.put(UserUtil.USER_ID, userDO.getUserId());
        claims.put(UserUtil.USERNAME, userDO.getUsername());
        claims.put(UserUtil.REAL_NAME, userDO.getRealName());
        claims.put(UserUtil.EMAIL, userDO.getEmail());
        claims.put(UserUtil.PHONE, userDO.getPhone());
        claims.put(UserUtil.ROLE, userDO.getRole());
        claims.put(UserUtil.GENDER, userDO.getGender());
        if (userDO.getBirthday() != null)
            claims.put(UserUtil.BIRTHDAY, DateTimeFormatter.ofPattern("yyyy-MM-dd").format(userDO.getBirthday()));
        String token = jwtUtil.generateToken(claims);
        redisTemplate.opsForValue().set(UserUtil.getTokenKey(userDO.getUserId()), token, jwtUtil.getExpiration(), TimeUnit.SECONDS);
        UserVO userVO = BeanUtil.toBean(userDO, UserVO.class);
        userVO.setToken(token);
        userVO.setPassword(null);
        return userVO;
    }

    @Override
    public void logout() {
        redisTemplate.opsForValue().getOperations().delete(UserUtil.getTokenKey());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void changePassword(String oldPassword, String newPassword) {
        String userId = UserUtil.getUserId();
        UserDO user = getById(userId);
        // 校验旧密码
        if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
            throw new ExamProException("原密码错误!");
        }
        // 更新密码
        user.setPassword(passwordEncoder.encode(newPassword));
        updateById(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void resetPassword(String id, String password) {
        UserDO user = new UserDO();
        // 重置为默认密码
        user.setUserId(Long.valueOf(id));
        user.setPassword(passwordEncoder.encode(password));
        updateById(user);
    }

    @Override
    public List<UserVO> getUsernameList(List<String> ids) {
        LambdaQueryWrapper<UserDO> wrapper = Wrappers.lambdaQuery();
        wrapper.select(UserDO::getUserId, UserDO::getUsername);
        wrapper.in(UserDO::getUserId, ids);
        List<UserDO> userDOS = baseMapper.selectList(wrapper);
        return BeanUtil.copyToList(userDOS, UserVO.class);
    }

    @Override
    public void sendCode(UserVO userVO) {
        if (StringUtil.isNotBlank(userVO.getPhone())) {
            String key = REGISTER_CODE + userVO.getPhone();
            String codeOld = redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(codeOld)) {
                Long expire = redisTemplate.getExpire(key);
                if (expire > 4 * 60) {
                    ExamProException.cast("请勿频繁发送验证码！");
                }
            }
            String code = RandomUtil.randomNumbers(6);
            redisTemplate.opsForValue().set(key, code, 5, TimeUnit.MINUTES);
//            todo 通手机号发送验证码
            emailUtil.sendText("2645558121@qq.com", "注册验证码-考试神器", "您的验证码是" + code + "，5分钟内有效。请勿泄露给他人!");
        } else if (StringUtil.isNotBlank(userVO.getEmail())) {
            String key = REGISTER_CODE + userVO.getEmail();
            String codeOld = redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(codeOld)) {
                Long expire = redisTemplate.getExpire(key);
                if (expire > 4 * 60)
                    ExamProException.cast("请勿频繁发送验证码！");
            }
            String code = RandomUtil.randomNumbers(6);
            redisTemplate.opsForValue().set(key, code, 5, TimeUnit.MINUTES);
            emailUtil.sendText(userVO.getEmail(), "注册验证码-考试神器", "您的验证码是" + code + "，5分钟内有效。请勿泄露给他人!");
        }
    }

    @Override
    public void addUser(UserVO user) {
        UserDO userDO = BeanUtil.toBean(user, UserDO.class);
        // 默认密码
        if (StrUtil.isEmpty(userDO.getPassword()))
            userDO.setPassword("123456");
        userDO.setPassword(passwordEncoder.encode(userDO.getPassword()));
        save(userDO);
    }

    @Override
    public UserVO getCurrentUser() {
        String userId = UserUtil.getUserId();
        UserVO userVO = BeanUtil.toBean(getById(userId), UserVO.class);
        userVO.setPassword(null);
        return userVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCurrentUser(UserVO user) {
        String userId = UserUtil.getUserId();
        if (!userId.equals(user.getUserId())) {
            throw new ExamProException("无法修改其他用户的信息");
        }
        
        UserDO userDO = BeanUtil.toBean(user, UserDO.class);
        updateById(userDO);
    }
}